package com.mf.aspect.debounces;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import com.mf.entity.dto.basic.DtoUser;
import com.mf.exception.GraceException;
import com.mf.utils.UtilRedisClient;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * Created by Luke on 2025/3/9 16:34.
 */
@Aspect
@Component
@Order(1)
public class DebounceAspect {

    @Resource
    private UtilRedisClient redisClient;

    @Around("execution(* com.mf.controller..*.*(..)) && (@annotation(debounce) || @within(debounce))")
    public Object debounce(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable {
        // 获取合并后的注解（优先取方法，再取类）
        Debounce finalDebounce = getMergedAnnotation(joinPoint, Debounce.class);

        String key = generateKey(joinPoint);
        boolean isAbsent = redisClient.setNx(key, UtilRedisClient.DEFAULT_VALUE, finalDebounce.interval(), TimeUnit.MILLISECONDS);

        if (!isAbsent) {
            GraceException.display("请求过快");
        }

        return joinPoint.proceed();
    }

    /**
     * 获取方法或类上的注解，方法注解优先级高于类注解
     */
    private <T extends Annotation> T getMergedAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        T methodAnn = method.getAnnotation(annotationClass);
        if (methodAnn != null) {
            return methodAnn;
        }

        Class<?> targetClass = joinPoint.getTarget().getClass();
        return targetClass.getAnnotation(annotationClass);
    }


    /**
     * 生成唯一标识Key（用户ID + 方法签名 + 参数哈希）
     *
     * @param joinPoint ProceedingJoinPoint
     * @return String
     */
    private String generateKey(ProceedingJoinPoint joinPoint) {
        StringBuilder sb = new StringBuilder();
        // 添加用户ID（根据实际项目获取用户信息，例如从Token中解析）
        try {

            DtoUser user = StpUtil.getSession().getModel(SaSession.USER, DtoUser.class);
            sb.append(user.getId()).append(":");
        } catch (Exception e) {
            sb.append("0:");
        }
        // 添加方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        sb.append(signature.getMethod().getDeclaringClass().getName())
                .append("#")
                .append(signature.getMethod().getName());
        // 添加参数哈希（防止不同参数重复）
        Object[] args = joinPoint.getArgs();
        if (args.length > 0) {
            String params = Arrays.deepToString(args);
            String paramsHash = DigestUtils.md5DigestAsHex(params.getBytes());
            sb.append(":").append(paramsHash);
        }
        return "debounce:" + sb;
    }
}
